package com.ihr360.payroll.service.alps;

import com.google.common.collect.Lists;
import com.google.gson.TypeAdapter;
import com.ihr360.alps.ApplicationDescriptor;
import com.ihr360.alps.PropertyDescriptor;
import com.ihr360.alps.ServiceDescriptor;
import com.ihr360.alps.api.AlpsProvider;
import com.ihr360.alps.api.IDListRequest;
import com.ihr360.alps.api.IDListWithCriteriaRequest;
import com.ihr360.alps.api.ServiceProvider;
import com.ihr360.common.DateRange;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.utils.JpaCriteriaPathUtils;
import com.ihr360.feign.interceptor.AuthUserInfoInterceptor;
import com.ihr360.payroll.dao.ReferenceApplicationRepository;
import com.ihr360.payroll.dao.ReferencePropertyRepository;
import com.ihr360.payroll.dao.ReferenceServiceRepository;
import com.ihr360.payroll.model.dto.reference.ReferenceApplication;
import com.ihr360.payroll.model.dto.reference.ReferenceProperty;
import com.ihr360.payroll.model.dto.reference.ReferenceService;
import com.ihr360.validation.ValidationUtils;
import feign.Logger;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.hystrix.HystrixFeign;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service
public class AlpsSyncServiceImpl implements AlpsSyncService {
    org.slf4j.Logger logger = LoggerFactory.getLogger(AlpsSyncService.class);
    @Autowired
    private ReferenceApplicationRepository refAppRepository;

    @Autowired
    private ReferenceServiceRepository refServiceRepository;

    @Autowired
    ReferencePropertyRepository refPropertyRepository;

    @Autowired
    private AuthUserInfoInterceptor authUserInfoInterceptor;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean syncApplications() {
        Iterable<ReferenceApplication> apps = refAppRepository.findAll();
        if (apps == null) {
            throw new Ihr360Exception(null, "未能获取到引用应用信息");
        }
        Iterator<ReferenceApplication> iterator = apps.iterator();


        HystrixFeign.Builder builder = HystrixFeign.builder()
                .logLevel(Logger.Level.FULL)
                .encoder(new GsonEncoder())
                .decoder(new GsonDecoder());


        Iterable<ReferenceService> refServicesIterable = refServiceRepository.findAll();

        Map<String, ReferenceService> serviceMap = StreamSupport.stream(refServicesIterable.spliterator(), false)
                .collect(Collectors.toMap(ReferenceService::getRefId, x -> x));
        List<String> serviceRefIds = Lists.newArrayList(serviceMap.keySet());

        Iterable<ReferenceProperty> referencePropertyIterable = refPropertyRepository.findAll();
        Map<String, ReferenceProperty> propertyMap = StreamSupport.stream(referencePropertyIterable.spliterator(), false)
                .collect(Collectors.toMap(ReferenceProperty::getKey, x -> x));
        List<String> servicePropertyRefIds = Lists.newArrayList(propertyMap.keySet());
        List<String> toRemoveReferenceServicesRefApplicationIds = Lists.newArrayList();

        List<ReferenceService> toSaveReferenceServices = Lists.newArrayList();
        List<ReferenceProperty> toSaveReferenceProperties = Lists.newArrayList();

        while (iterator != null && iterator.hasNext()) {
            ReferenceApplication application = iterator.next();
            String baseUrl = application.getBaseUrl();
            ApplicationDescriptor descriptor = null;
            try {
                AlpsProvider provider = builder
                        .target(AlpsProvider.class, baseUrl);
                descriptor = provider.applicationDescriptor();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }

            if (descriptor == null) {
                continue;
            }
            List<ServiceDescriptor> services = descriptor.getServices();
            if (CollectionUtils.isEmpty(services)) {
//                toRemoveReferenceServicesRefApplicationIds.add(descriptor.getId());
                continue;
            }
            serviceRefIds.removeAll(services.stream().map(ServiceDescriptor::getServiceId).collect(Collectors.toList()));

            for (ServiceDescriptor service : services) {
                List<PropertyDescriptor> descriptors = service.getDescriptors();
//
                ReferenceService referenceService = serviceMap.get(service.getServiceId());
                if (referenceService == null) {
                    referenceService = ReferenceService.builder()
                            .refApplication(application)
                            .refId(service.getServiceId())
                            .refName(service.getName())
                            .path(service.getPath())
                            .build();
                } else {
                    referenceService.setRefName(service.getName());
                    referenceService.setPath(service.getPath());
                    referenceService.getProperties().clear();
                }
                if (CollectionUtils.isNotEmpty(descriptors)) {
                    List<ReferenceProperty> referenceProperties = new ArrayList<>(descriptors.size());
                    for (PropertyDescriptor propertyDescriptor : descriptors) {
                        String key = service.getServiceId().concat(".").concat(propertyDescriptor.getId());
                        ReferenceProperty property = propertyMap.get(key);
                        ReferenceProperty referenceProperty = ReferenceProperty.builder()
                                .refId(propertyDescriptor.getId())
                                .refName(propertyDescriptor.getName())
                                .refService(referenceService)
                                .type(propertyDescriptor.getType())
                                .build();
                        if (property != null) {
                            referenceProperty.setId(property.getId());
                            servicePropertyRefIds.remove(key);
                        }
                        referenceProperties.add(referenceProperty);
                    }
                    toSaveReferenceProperties.addAll(referenceProperties);
                }
                toSaveReferenceServices.add(referenceService);

            }
        }

        if (CollectionUtils.isNotEmpty(servicePropertyRefIds)) {
            Specification<ReferenceProperty> toRemoveSpecification = getToRemoveReferenceProperty(servicePropertyRefIds);
            List<ReferenceProperty> toRemoveProperties = refPropertyRepository.findAll(toRemoveSpecification);
            if (CollectionUtils.isNotEmpty(toRemoveProperties)) {
                refPropertyRepository.delete(toRemoveProperties);
            }
        }
        if (CollectionUtils.isNotEmpty(serviceRefIds)) {
            List<ReferenceService> toRemoveService = refServiceRepository.findByRefIdIn(serviceRefIds);
            if (CollectionUtils.isNotEmpty(toRemoveService)) {
                refServiceRepository.delete(toRemoveService);
            }
        }

        if (CollectionUtils.isNotEmpty(toRemoveReferenceServicesRefApplicationIds)) {
            List<ReferenceService> referenceServices = refServiceRepository.findByRefIdIn(toRemoveReferenceServicesRefApplicationIds);
            if (CollectionUtils.isNotEmpty(referenceServices)) {
                refServiceRepository.delete(referenceServices);
            }
        }

        if (CollectionUtils.isNotEmpty(toSaveReferenceServices)) {
            if (CollectionUtils.isNotEmpty(toSaveReferenceProperties)) {
                toSaveReferenceServices.forEach(refService -> {
                    toSaveReferenceProperties.forEach(property -> {
                        if (property.getRefService().getRefId().equals(refService.getRefId())) {
                            List<ReferenceProperty> referenceProperties = refService.getProperties();
                            if (referenceProperties == null) {
                                referenceProperties = Lists.newArrayList();
                                refService.setProperties(referenceProperties);
                            }
                            refService.getProperties().add(property);
                        }
                    });
                });
            }
            refServiceRepository.save(toSaveReferenceServices);
        }
        return true;
    }

    @Override
    public Map<String, Map<String, Object>> getDataByService(String companyId, ReferenceService refService, Collection<String> staffIds, DateRange payrollPeriodRange, DateRange attendancePeriodRange) {
        Map<String, Map<String, Object>> result = null;
        try {
            HystrixFeign.Builder builder = HystrixFeign.builder()
                    .logLevel(Logger.Level.FULL)
                    .encoder(new GsonEncoder())
                    .decoder(new GsonDecoder(new Iterable<TypeAdapter<?>>() {
                        @Override
                        public Iterator<TypeAdapter<?>> iterator() {
                            List<TypeAdapter<?>> list = new ArrayList<TypeAdapter<?>>(1);
                            list.add(new com.ihr360.payroll.service.alps.decoder.DoubleToIntMapTypeAdapter());
                            return list.iterator();
                        }
                    }))
                    .requestInterceptor(authUserInfoInterceptor);

            String reqUrl = refService.getRefApplication().getBaseUrl().concat(refService.getPath());
            ServiceProvider provider = builder
                    .target(ServiceProvider.class, reqUrl);

            ValidationUtils.rejectIfEmpty(companyId, null, "无法获取有效公司标志");

            String serviceId = refService.getRefId();
            switch (serviceId) {
                case ServiceProvider.SERVICE_ID.STAFF_ROSTER:
                    result = provider.getItems(companyId, IDListRequest.build(staffIds));
                    break;
                case ServiceProvider.SERVICE_ID.SIHF:
                    result = provider.getItems(companyId, payrollPeriodRange.getFromYear(), payrollPeriodRange.getFromMonth(), new IDListWithCriteriaRequest<>(staffIds));
                    break;
                default:
                    return null;
            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new Ihr360Exception(null, String.format("从%s数据获取异常", refService.getRefName()));
        }
//        List<ReferenceProperty> referenceProperties = refService.getProperties();
//        if (result != null) {
//            result.entrySet().stream().map(e -> {
//                e.getKey();
//                e.getValue();
//            }).collect(Co)
//        }
        return result;
    }

    private Specification<ReferenceProperty> getToRemoveReferenceProperty(List<String> servicePropertyRefIds) {
        return (Root<ReferenceProperty> root, CriteriaQuery<?> q, CriteriaBuilder cb) -> {
            Predicate predicate = cb.conjunction();
            Expression propertyRefIdExpress = JpaCriteriaPathUtils.getPath(root, "refId").as(String.class);
            Expression serviceRefIdExpress = JpaCriteriaPathUtils.getPath(root, "refService.refId").as(String.class);
            List<Predicate> refPredicates = Lists.newArrayList();
            servicePropertyRefIds.forEach(serviceProperytyRefId -> {

                String[] refIds = serviceProperytyRefId.split("\\.");
                String serviceRefId = refIds[0];
                String propertyRefId = refIds[1];
                Predicate serviceRefIdPredicate = cb.equal(serviceRefIdExpress, serviceRefId);
                Predicate propertyRefIdEqualsPredicate = cb.equal(propertyRefIdExpress, propertyRefId);
                refPredicates.add(cb.or(cb.and(serviceRefIdPredicate, propertyRefIdEqualsPredicate)));
            });
            Predicate p = cb.or(refPredicates.toArray(new Predicate[refPredicates.size()]));
            predicate.getExpressions().add(p);
            q.where(predicate);
            return q.getRestriction();
        };
    }
}